/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.frontends.functional;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Laurent WOUTERS
 */
public class FParser {
    protected static final String PN_CHARS_BASE = "[A-Z]|[a-z]|[\\u00C0-\\u00D6]|[\\u00D8-\\u00F6]|[\\u00F8-\\u02FF]|[\\u0370-\\u037D]|[\\u037F-\\u1FFF]|[\\u200C-\\u200D]|[\\u2070-\\u218F]|[\\u2C00-\\u2FEF]|[\\u3001-\\uD7FF]|[\\uF900-\\uFDCF]|[\\uFDF0-\\uFFFD]";
    protected static final String PN_CHARS_U    = "(" + PN_CHARS_BASE + ")|_";
    protected static final String PN_CHARS      = "(" + PN_CHARS_U + ")|-|[0-9]|\\u00B7|[\\u0300-\\u036F]|[\\u203F-\\u2040]";
    protected static final String PN_PREFIX     = "(" + PN_CHARS_BASE + ")(((" + PN_CHARS + ")|\\.)*(" + PN_CHARS + "))?";
    protected static final String PN_LOCAL      = "(" + PN_CHARS_U + "|[0-9])(((" + PN_CHARS + ")|\\.)*(" + PN_CHARS + "))?";
    protected static final String PNAME_NS      = "(" + PN_PREFIX + ")?:";
    protected static final String PNAME_LN      = PNAME_NS + PN_LOCAL;

    protected static final String expFullIRI = "<([^>]+)>";
    protected static final String expAbbrIRI = PNAME_LN;
    protected static final String expIRI = "(" + expFullIRI + "|" + expAbbrIRI + ")";
    protected static final String expNodeID = "_:" + PN_LOCAL;
    protected static final String expPrefixDef = PNAME_NS + "\\s*=\\s*" + expFullIRI;
    protected static final String expLitTyped = "\"([^\"]*)\"(\\^\\^" + expIRI + ")?";
    protected static final String expLitNum = "(\\+|-)?\\d+(\\.\\d*)?";
    protected static final String expLiteral = "(" + expLitTyped + "|" + expLitNum + ")";
    protected static final String expVarName = "[_a-zA-Z0-9]+";
    protected static final String expQueryVar = "\\?" + expVarName;
    protected static final String expCodeVar = "\\$" + expVarName;
    protected static final String expWord = "\\w+";
    protected static final String expBlanck = "\\s+";
    protected static final String expNewLine = "\n|\r|\n\r";

    protected Pattern pExpPrefixDef;
    protected Pattern pExpIRI;
    protected Pattern pExpNodeID;
    protected Pattern pExpLiteral;
    protected Pattern pExpWord;
    protected Pattern pExpQueryVar;
    protected Pattern pExpCodeVar;
    protected Pattern pExpBlanck;
    protected Map<String, String> mapPrefixes;
    protected String document;
    protected String content;
    protected int line;

    public FParser() { }

    public FNode parse(String docName, String text) throws FParserException {
        pExpPrefixDef = Pattern.compile(expPrefixDef);
        pExpIRI = Pattern.compile(expIRI);
        pExpNodeID = Pattern.compile(expNodeID);
        pExpLiteral = Pattern.compile(expLiteral);
        pExpWord = Pattern.compile(expWord);
        pExpQueryVar = Pattern.compile(expQueryVar);
        pExpCodeVar = Pattern.compile(expCodeVar);
        pExpBlanck = Pattern.compile(expBlanck);
        mapPrefixes = new HashMap<String, String>();
        document = docName;
        content = text;
        line = 1;
        return parse_Document();
    }

    public FNode parseInline(String text) throws FParserException {
        pExpPrefixDef = Pattern.compile(expPrefixDef);
        pExpIRI = Pattern.compile(expIRI);
        pExpNodeID = Pattern.compile(expNodeID);
        pExpLiteral = Pattern.compile(expLiteral);
        pExpWord = Pattern.compile(expWord);
        pExpQueryVar = Pattern.compile(expQueryVar);
        pExpCodeVar = Pattern.compile(expCodeVar);
        pExpBlanck = Pattern.compile(expBlanck);
        mapPrefixes = new HashMap<String, String>();
        document = "inline";
        content = text;
        line = 1;
        skip();
        Matcher matcher = pExpLiteral.matcher(content);
        if (matcher.lookingAt()) {
            // Found literal
            String match = matcher.group();
            return parse_Literal(match, line);
        }
        matcher = pExpWord.matcher(content);
        if (matcher.lookingAt()) {
            // Found Word
            String name = matcher.group();
            FElementNode element = new FElementNode(name, line);
            skip(name.length());
            parse_Content(element);
            return element;
        }
        return null;
    }

    protected FNode parse_Document() throws FParserException {
        FElementNode docNode = new FElementNode("document", 0);
        skip();
        while (!content.isEmpty()) {
            Matcher matcher = pExpWord.matcher(content);
            if (matcher.lookingAt()) {
                // Found Word
                String name = matcher.group();
                if (name.equals(xowl.interpreter.frontends.VocOWL2.ontoPrefix)) {
                    skip(name.length());
                    parse_Prefix();
                } else if (name.equals(xowl.interpreter.frontends.VocOWL2.ontoOntology)) {
                    FElementNode element = new FElementNode(name, line);
                    skip(name.length());
                    parse_Content(element);
                    docNode.getChildren().add(element);
                }
            }
        }
        return docNode;
    }

    protected void parse_Prefix() throws FParserException {
        skip();
        if (!content.startsWith("("))
            throw new FParserException(document, line, "Malformed prefix definition, expected '('");
        skip(1);
        Matcher matcher = pExpPrefixDef.matcher(content);
        if (!matcher.lookingAt())
            throw new FParserException(document, line, "Malformed prefix definition, expected prefix definition");
        String[] parts = matcher.group().split("=");
        String prefix = parts[0].trim();
        prefix = prefix.substring(0, prefix.length() - 1);
        String uri = parts[1].trim();
        uri = uri.substring(1, uri.length() - 1);
        mapPrefixes.put(prefix, uri);
        skip(matcher.group().length());
        skip(1);
    }

    protected void parse_Content(FElementNode parent) throws FParserException {
        skip();
        if (!content.startsWith("("))
            throw new FParserException(document, line, "Malformed element node, expected '('");
        skip(1);
        while (!content.startsWith(")")) {
            Matcher matcher = pExpQueryVar.matcher(content);
            if (matcher.lookingAt()) {
                // Found QVar
                FQueryVar child = new FQueryVar(matcher.group().substring(1), line);
                parent.getChildren().add(child);
                skip(matcher.group().length());
                continue;
            }
            matcher = pExpCodeVar.matcher(content);
            if (matcher.lookingAt()) {
                // Found CVar
                FCodeVar child = new FCodeVar(matcher.group().substring(1), line);
                parent.getChildren().add(child);
                skip(matcher.group().length());
                continue;
            }
            matcher = pExpLiteral.matcher(content);
            if (matcher.lookingAt()) {
                // Found literal
                String match = matcher.group();
                parent.getChildren().add(parse_Literal(match, line));
                skip(match.length());
                continue;
            }
            matcher = pExpIRI.matcher(content);
            if (matcher.lookingAt()) {
                // Found IRI
                FNode child = parse_IRINode(matcher.group(), line);
                if (!parent.isAnonymous() && parent.getName().equals(xowl.interpreter.frontends.VocOWL2.ontoOntology))
                    mapPrefixes.put("", ((FIRINode)child).getIRI() + "#");
                parent.getChildren().add(child);
                skip(matcher.group().length());
                continue;
            }
            matcher = pExpNodeID.matcher(content);
            if (matcher.lookingAt()) {
                // Found node ID
                FNodeIDNode child = new FNodeIDNode(matcher.group(), line);
                parent.getChildren().add(child);
                skip(matcher.group().length());
                continue;
            }
            if (content.startsWith("(")) {
                FElementNode element = new FElementNode(null, line);
                parse_Content(element);
                parent.getChildren().add(element);
                continue;
            }
            matcher = pExpWord.matcher(content);
            if (matcher.lookingAt()) {
                // Found Word
                String name = matcher.group();
                FElementNode element = new FElementNode(name, line);
                skip(name.length());
                parse_Content(element);
                parent.getChildren().add(element);
                continue;
            }
        }
        skip(1);
    }

    protected FNode parse_Literal(String content, int line) throws FParserException {
        if (content.contains("^^")) {
            // Typed literal
            String[] parts = content.split("\\^\\^", -1);
            String value = parts[0].substring(1, parts[0].length() - 1);
            String type = null;
            if (parts[1].isEmpty()) type = xowl.interpreter.impl.OWLDatatype.xsdString;
            else type = parse_IRI(parts[1], line);
            return new FLiteralNode(value, type, line);
        } else {
            // Implicitly typed
            if (content.startsWith("\"")) {
                // Implicit string
                return new FLiteralNode(content.substring(1, content.length() - 1), xowl.interpreter.impl.OWLDatatype.xsdString, line);
            }
            // Numeric literal
            if (content.contains("."))
                return new FLiteralNode(content, xowl.interpreter.impl.OWLDatatype.xsdFloat, line);
            else
                return new FLiteralNode(content, xowl.interpreter.impl.OWLDatatype.xsdInteger, line);
        }
    }

    protected FNode parse_IRINode(String content, int line) throws FParserException {
        return new FIRINode(parse_IRI(content, line), line);
    }

    protected String parse_IRI(String content, int line) throws FParserException {
        if (content.startsWith("<"))
            return content.substring(1, content.length() - 1);
        String[] parts = content.split(":", -1);
        if (!mapPrefixes.containsKey(parts[0]))
            throw new FParserException(document, line, "Prefix " + parts[0] + " is undefined.");
        return mapPrefixes.get(parts[0]) + parts[1];
    }

    protected void skip() { skip(0); }
    protected void skip(int i) {
        if (i != 0) {
            if (content.length() <= i) {
                content = "";
                return;
            }
            content = content.substring(i);
        }
        Matcher matcher = pExpBlanck.matcher(content);
        if (matcher.lookingAt()) {
            String blanck = matcher.group();
            line += blanck.split(expNewLine, -1).length - 1;
            if (content.length() <= blanck.length())
                content = "";
            else
                content = content.substring(blanck.length());
        }
    }
}
